/*!
 * jquery.event.drop - v 2.2
 * Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
 * Open Source MIT License - http://threedubmedia.com/code/license
 */
// Created: 2008-06-04
// Updated: 2012-05-21
// REQUIRES: jquery 1.7.x, event.drag 2.2

;(function($){ // secure $ jQuery alias

// Events: drop, dropstart, dropend

// add the jquery instance method
$.fn.drop = function( str, arg, opts ){
  // figure out the event type
  var type = typeof str == "string" ? str : "",
  // figure out the event handler...
  fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null;
  // fix the event type
  if ( type.indexOf("drop") !== 0 )
    type = "drop"+ type;
  // were options passed
  opts = ( str == fn ? arg : opts ) || {};
  // trigger or bind event handler
  return fn ? this.bind( type, opts, fn ) : this.trigger( type );
};

// DROP MANAGEMENT UTILITY
// returns filtered drop target elements, caches their positions
$.drop = function( opts ){
  opts = opts || {};
  // safely set new options...
  drop.multi = opts.multi === true ? Infinity :
    opts.multi === false ? 1 : !isNaN( opts.multi ) ? opts.multi : drop.multi;
  drop.delay = opts.delay || drop.delay;
  drop.tolerance = $.isFunction( opts.tolerance ) ? opts.tolerance :
    opts.tolerance === null ? null : drop.tolerance;
  drop.mode = opts.mode || drop.mode || 'intersect';
};

// local refs (increase compression)
var $event = $.event,
$special = $event.special,
// configure the drop special event
drop = $.event.special.drop = {

  // these are the default settings
  multi: 1, // allow multiple drop winners per dragged element
  delay: 20, // async timeout delay
  mode: 'overlap', // drop tolerance mode

  // internal cache
  targets: [],

  // the key name for stored drop data
  datakey: "dropdata",

  // prevent bubbling for better performance
  noBubble: true,

  // count bound related events
  add: function( obj ){
    // read the interaction data
    var data = $.data( this, drop.datakey );
    // count another realted event
    data.related += 1;
  },

  // forget unbound related events
  remove: function(){
    $.data( this, drop.datakey ).related -= 1;
  },

  // configure the interactions
  setup: function(){
    // check for related events
    if ( $.data( this, drop.datakey ) )
      return;
    // initialize the drop element data
    var data = {
      related: 0,
      active: [],
      anyactive: 0,
      winner: 0,
      location: {}
    };
    // store the drop data on the element
    $.data( this, drop.datakey, data );
    // store the drop target in internal cache
    drop.targets.push( this );
  },

  // destroy the configure interaction
  teardown: function(){
    var data = $.data( this, drop.datakey ) || {};
    // check for related events
    if ( data.related )
      return;
    // remove the stored data
    $.removeData( this, drop.datakey );
    // reference the targeted element
    var element = this;
    // remove from the internal cache
    drop.targets = $.grep( drop.targets, function( target ){
      return ( target !== element );
    });
  },

  // shared event handler
  handler: function( event, dd ){
    // local vars
    var results, $targets;
    // make sure the right data is available
    if ( !dd )
      return;
    // handle various events
    switch ( event.type ){
      // draginit, from $.event.special.drag
      case 'mousedown': // DROPINIT >>
      case 'touchstart': // DROPINIT >>
        // collect and assign the drop targets
        $targets =  $( drop.targets );
        if ( typeof dd.drop == "string" )
          $targets = $targets.filter( dd.drop );
        // reset drop data winner properties
        $targets.each(function(){
          var data = $.data( this, drop.datakey );
          data.active = [];
          data.anyactive = 0;
          data.winner = 0;
        });
        // set available target elements
        dd.droppable = $targets;
        // activate drop targets for the initial element being dragged
        $special.drag.hijack( event, "dropinit", dd );
        break;
      // drag, from $.event.special.drag
      case 'mousemove': // TOLERATE >>
      case 'touchmove': // TOLERATE >>
        drop.event = event; // store the mousemove event
        if ( !drop.timer )
          // monitor drop targets
          drop.tolerate( dd );
        break;
      // dragend, from $.event.special.drag
      case 'mouseup': // DROP >> DROPEND >>
      case 'touchend': // DROP >> DROPEND >>
        drop.timer = clearTimeout( drop.timer ); // delete timer
        if ( dd.propagates ){
          $special.drag.hijack( event, "drop", dd );
          $special.drag.hijack( event, "dropend", dd );
        }
        break;

    }
  },

  // returns the location positions of an element
  locate: function( elem, index ){
    var data = $.data( elem, drop.datakey ),
    $elem = $( elem ),
    posi = $elem.offset() || {},
    height = $elem.outerHeight(),
    width = $elem.outerWidth(),
    location = {
      elem: elem,
      width: width,
      height: height,
      top: posi.top,
      left: posi.left,
      right: posi.left + width,
      bottom: posi.top + height
    };
    // drag elements might not have dropdata
    if ( data ){
      data.location = location;
      data.index = index;
      data.elem = elem;
    }
    return location;
  },

  // test the location positions of an element against another OR an X,Y coord
  contains: function( target, test ){ // target { location } contains test [x,y] or { location }
    return ( ( test[0] || test.left ) >= target.left && ( test[0] || test.right ) <= target.right
      && ( test[1] || test.top ) >= target.top && ( test[1] || test.bottom ) <= target.bottom );
  },

  // stored tolerance modes
  modes: { // fn scope: "$.event.special.drop" object
    // target with mouse wins, else target with most overlap wins
    'intersect': function( event, proxy, target ){
      return this.contains( target, [ event.pageX, event.pageY ] ) ? // check cursor
        1e9 : this.modes.overlap.apply( this, arguments ); // check overlap
    },
    // target with most overlap wins
    'overlap': function( event, proxy, target ){
      // calculate the area of overlap...
      return Math.max( 0, Math.min( target.bottom, proxy.bottom ) - Math.max( target.top, proxy.top ) )
        * Math.max( 0, Math.min( target.right, proxy.right ) - Math.max( target.left, proxy.left ) );
    },
    // proxy is completely contained within target bounds
    'fit': function( event, proxy, target ){
      return this.contains( target, proxy ) ? 1 : 0;
    },
    // center of the proxy is contained within target bounds
    'middle': function( event, proxy, target ){
      return this.contains( target, [ proxy.left + proxy.width * .5, proxy.top + proxy.height * .5 ] ) ? 1 : 0;
    }
  },

  // sort drop target cache by by winner (dsc), then index (asc)
  sort: function( a, b ){
    return ( b.winner - a.winner ) || ( a.index - b.index );
  },

  // async, recursive tolerance execution
  tolerate: function( dd ){
    // declare local refs
    var i, drp, drg, data, arr, len, elem,
    // interaction iteration variables
    x = 0, ia, end = dd.interactions.length,
    // determine the mouse coords
    xy = [ drop.event.pageX, drop.event.pageY ],
    // custom or stored tolerance fn
    tolerance = drop.tolerance || drop.modes[ drop.mode ];
    // go through each passed interaction...
    do if ( ia = dd.interactions[x] ){
      // check valid interaction
      if ( !ia )
        return;
      // initialize or clear the drop data
      ia.drop = [];
      // holds the drop elements
      arr = [];
      len = ia.droppable.length;
      // determine the proxy location, if needed
      if ( tolerance )
        drg = drop.locate( ia.proxy );
      // reset the loop
      i = 0;
      // loop each stored drop target
      do if ( elem = ia.droppable[i] ){
        data = $.data( elem, drop.datakey );
        drp = data.location;
        if ( !drp ) continue;
        // find a winner: tolerance function is defined, call it
        data.winner = tolerance ? tolerance.call( drop, drop.event, drg, drp )
          // mouse position is always the fallback
          : drop.contains( drp, xy ) ? 1 : 0;
        arr.push( data );
      } while ( ++i < len ); // loop
      // sort the drop targets
      arr.sort( drop.sort );
      // reset the loop
      i = 0;
      // loop through all of the targets again
      do if ( data = arr[ i ] ){
        // winners...
        if ( data.winner && ia.drop.length < drop.multi ){
          // new winner... dropstart
          if ( !data.active[x] && !data.anyactive ){
            // check to make sure that this is not prevented
            if ( $special.drag.hijack( drop.event, "dropstart", dd, x, data.elem )[0] !== false ){
              data.active[x] = 1;
              data.anyactive += 1;
            }
            // if false, it is not a winner
            else
              data.winner = 0;
          }
          // if it is still a winner
          if ( data.winner )
            ia.drop.push( data.elem );
        }
        // losers...
        else if ( data.active[x] && data.anyactive == 1 ){
          // former winner... dropend
          $special.drag.hijack( drop.event, "dropend", dd, x, data.elem );
          data.active[x] = 0;
          data.anyactive -= 1;
        }
      } while ( ++i < len ); // loop
    } while ( ++x < end ) // loop
    // check if the mouse is still moving or is idle
    if ( drop.last && xy[0] == drop.last.pageX && xy[1] == drop.last.pageY )
      delete drop.timer; // idle, don't recurse
    else  // recurse
      drop.timer = setTimeout(function(){
        drop.tolerate( dd );
      }, drop.delay );
    // remember event, to compare idleness
    drop.last = drop.event;
  }

};

// share the same special event configuration with related events...
$special.dropinit = $special.dropstart = $special.dropend = drop;

})(jQuery); // confine scope